﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace LogManager.TestUtility
{
    public static class NotifyPropertyChangedHelpers
    {
        public static NotifyExpectation<T> ShouldNotifyOn<T, TProperty>(this T owner, Expression<Func<T, TProperty>> propertyPicker) where T : INotifyPropertyChanged
        {
            return CreateExpectation(owner, propertyPicker, true);
        }

        public static NotifyExpectation<T> ShouldNotNotifyOn<T, TProperty>(this T owner, Expression<Func<T, TProperty>> propertyPicker) where T : INotifyPropertyChanged
        {
            return CreateExpectation(owner, propertyPicker, false);
        }

        private static NotifyExpectation<T> CreateExpectation<T, TProperty>(T owner, Expression<Func<T, TProperty>> pickProperty, bool eventExpected) where T : INotifyPropertyChanged
        {
            var propInfo = (PropertyInfo)((MemberExpression)pickProperty.Body).Member;
            return new NotifyExpectation<T>(owner, propInfo.Name, eventExpected);
        }

        public class NotifyExpectation<T> where T : INotifyPropertyChanged
        {
            private readonly T owner;
            private readonly string propertyName;
            private readonly bool eventExpected;

            public NotifyExpectation(T owner, string propertyName, bool eventExpected)
            {
                this.owner = owner;
                this.propertyName = propertyName;
                this.eventExpected = eventExpected;
            }

            public void When(Action<T> action)
            {
                bool eventWasRaised = false;
                this.owner.PropertyChanged += (sender, e) =>
                {
                    if (e.PropertyName == this.propertyName)
                    {
                        eventWasRaised = true;
                    }
                };
                action(this.owner);

                if (this.eventExpected != eventWasRaised)
                {
                    var msg = string.Format("Expecting change on {0}", this.propertyName);
                    msg += "\n" + "Expected:" + this.eventExpected + " Raised:" + eventWasRaised;
                    throw new Exception(msg);
                }
            }

            public void WhenModifing<TProperty>(Expression<Func<T, TProperty>> pickProperty)
            {
                var changeValue = default(TProperty);
                WhenModifing(pickProperty, changeValue);
            }

            public void WhenModifing<TProperty>(Expression<Func<T, TProperty>> pickProperty, TProperty value)
            {
                var propInfo = (PropertyInfo)((MemberExpression)pickProperty.Body).Member;

                Action<T> modificationAction = (p) => propInfo.SetMethod.Invoke(p, new object[] { value });
                When(modificationAction);
            }
        }
    }
}
